﻿//  --------------------------------
//  Copyright (c) AW Systems, Inc. All rights reserved.
//  This source code is made available under the terms of the Microsoft Public License (Ms-PL)
//  http://www.codeplex.com/SLAM/license
//  ---------------------------------

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Linq;
using System.Text;
using Slam.Configuration;
using Slam.Data;
using Slam.Events;

namespace Slam.Data
{
	public class SqlDataServices : IDataServices
	{
		#region Private State

		/// <summary>
		/// Internal repository/data access layer 
		/// </summary>
		private SqlDataRepository _database;

		/// <summary>
		/// SLAM configuration defining the data model
		/// </summary>
		private IConfigurationManager _configurationManager;
		private string _dataSchema;

		#endregion

		#region Constructors

		/// <summary>
		/// Creates a new instance of DataServices for the given configuration.
		/// </summary>
		/// <param name="configuration"></param>
		public SqlDataServices(IConfigurationManager configuration)
		{
			_configurationManager = configuration;

			string connectionString = "";
			if (_configurationManager.DataMapping is XmlSqlDataMapping)
			{
				connectionString = ((XmlSqlDataMapping)_configurationManager.DataMapping).GetConnectionString();
			}

			if (String.IsNullOrEmpty(connectionString))
			{
				connectionString = _configurationManager.GetSection<ConnectionStringSection>("ConnectionStrings").Default;
			}
			else
			{
				connectionString = _configurationManager.GetSection<ConnectionStringSection>("ConnectionStrings").GetConnectionString(connectionString);
			}

			_database = new SqlDataRepository(connectionString);
		}

		~SqlDataServices()
		{
			_database.Dispose();
		}

		#endregion

		#region IDataServices Members

		/// <summary>
		/// Creates a new row in the association table.
		/// </summary>
		/// <param name="associationTypeName"></param>
		/// <param name="firstTypeName"></param>
		/// <param name="firstId"></param>
		/// <param name="secondTypeName"></param>
		/// <param name="secondId"></param>
		/// <returns></returns>
		public bool Associate(string associationTypeName, string firstTypeName, string firstId, string secondTypeName, string secondId)
		{
			EnsureDataSchema();
			_database.EnsureDatabaseExists();
			if (!_database.VerifyTableExists(_dataSchema, GetTableName(associationTypeName)))
			{
				//_database.CreateAssociationTable(_dataSchema, GetTableName(associationTypeName), GetTableName(firstTypeName), GetTableName(secondTypeName));
			}

			_database.InsertAssociation(_dataSchema, GetTableName(associationTypeName), firstTypeName, firstId, secondTypeName, secondId);

			return true;
		}

		public bool Associate(string associationTypeName, string firstTypeName, string firstId, string secondTypeName, string secondId, params object[] extendedParameters)
		{
			EnsureDataSchema();
			return Associate(associationTypeName, firstTypeName, firstId, secondTypeName, secondId);
		}

		/// <summary>
		/// Deletes a row in the table for the typeName with the given id
		/// </summary>
		/// <param name="typeName"></param>
		/// <param name="id"></param>
		/// <returns></returns>
		public bool Delete(string typeName, string id)
		{
			EnsureDataSchema();
			_database.EnsureDatabaseExists();
			if (_database.VerifyTableExists(_dataSchema, GetTableName(typeName)))
			{
				_database.DeleteItem(_dataSchema, GetTableName(typeName), id);
			}
			else
			{
				_database.CreateContentTable(_dataSchema, GetTableName(typeName), _configurationManager.DataMapping.GetTypeFields("", typeName) as IEnumerable<Field>);
			}
			return true;
		}

		public bool Delete(string typeName, string id, params object[] extendedParameters)
		{
			EnsureDataSchema();
			bool success = true;
			if (extendedParameters.Length == 2 && extendedParameters[0] is bool && (bool)extendedParameters[0])
			{
				_database.DeleteAttachments(_dataSchema, GetTableName(typeName), (string)extendedParameters[1], id);
			}
			else
			{
				success = Delete(typeName, id);
			}
			return success;
		}

		/// <summary>
		/// Deletes all associations of the given association type for the given type and id.
		/// </summary>
		/// <param name="associationTypeName"></param>
		/// <param name="typeName"></param>
		/// <param name="id"></param>
		/// <returns></returns>
		public bool DeleteAllAssociations(string associationTypeName, string typeName, string id)
		{
			EnsureDataSchema();
			_database.EnsureDatabaseExists();
			if (_database.VerifyTableExists(_dataSchema, GetTableName(associationTypeName)))
			{
				_database.DeleteAllAssociations(_dataSchema, GetTableName(associationTypeName), GetTableName(typeName), id);
			}
			return true;
		}

		public bool DeleteAllAssociations(string associationTypeName, string typeName, string id, params object[] extendedParameters)
		{
			EnsureDataSchema();
			return DeleteAllAssociations(associationTypeName, typeName, id);
		}

		/// <summary>
		/// Delete the association of the given association type for the given ids.
		/// </summary>
		/// <param name="associationTypeName"></param>
		/// <param name="firstTypeName"></param>
		/// <param name="firstId"></param>
		/// <param name="secondTypeName"></param>
		/// <param name="secondId"></param>
		/// <returns></returns>
		public bool DeleteAssociation(string associationTypeName, string firstTypeName, string firstId, string secondTypeName, string secondId)
		{
			EnsureDataSchema();
			_database.EnsureDatabaseExists();
			if (_database.VerifyTableExists(_dataSchema, GetTableName(associationTypeName)))
			{
				_database.DeleteAssociation(_dataSchema, GetTableName(associationTypeName), GetTableName(firstTypeName), firstId, GetTableName(secondTypeName), secondId);
			}
			return true;
		}

		public bool DeleteAssociation(string associationTypeName, string firstTypeName, string firstId, string secondTypeName, string secondId, params object[] extendedParameters)
		{
			EnsureDataSchema();
			return DeleteAssociation(associationTypeName, firstTypeName, firstId, secondTypeName, secondId);
		}

		/// <summary>
		/// Inserts a new row into the table for the typeName with the Hashtable of field names and values.
		/// </summary>
		/// <param name="typeName"></param>
		/// <param name="values"></param>
		/// <returns></returns>
		public bool Insert(string typeName, Hashtable values)
		{
			EnsureDataSchema();
			_database.EnsureDatabaseExists();

			if (!_database.VerifyTableExists(_dataSchema, GetTableName(typeName)))
			{
				_database.CreateContentTable(_dataSchema, GetTableName(typeName), _configurationManager.DataMapping.GetTypeFields("", typeName) as IEnumerable<Field>);
			}
			else
			{
				_database.UpdateContentTable(_dataSchema, GetTableName(typeName), _configurationManager.DataMapping.GetTypeFields("", typeName) as IEnumerable<Field>);
			}

			_database.InsertItem(_dataSchema, GetTableName(typeName), values);

			return true;
		}

		public bool Insert(string typeName, Hashtable values, params object[] extendedParameters)
		{
			EnsureDataSchema();
			bool success = true;
			if (extendedParameters.Length == 2 && extendedParameters[0] is bool && (bool)extendedParameters[0])
			{
				_database.EnsureDatabaseExists();

				Field idField = _configurationManager.DataMapping.GetTypeFields("", (string)extendedParameters[1]).ToList()[0];
				string idColumnName = idField.Name;
				string idColumnSqlType = "";
				if (idField is SqlMappedField)
				{
					idColumnName = String.IsNullOrEmpty(((SqlMappedField)idField).Column) ? idColumnName : ((SqlMappedField)idField).Column;
					idColumnSqlType = String.IsNullOrEmpty(((SqlMappedField)idField).SqlType) ? idColumnName : ((SqlMappedField)idField).SqlType;
				}

				if (!_database.VerifyTableExists(_dataSchema, GetTableName(typeName)))
				{
					_database.CreateAttachmentTable(_dataSchema, GetTableName(typeName), (string)extendedParameters[1], idColumnName, idColumnSqlType);
				}

				_database.InsertAttachment(_dataSchema, GetTableName(typeName), values);
			}
			else
			{
				success = Insert(typeName, values);
			}
			return success;
		}

		/// <summary>
		/// Updates a row in the table for the typeName of the given with id with the Hashtable of field names and values.
		/// </summary>
		/// <param name="typeName"></param>
		/// <param name="id"></param>
		/// <param name="values"></param>
		/// <returns></returns>
		public bool Update(string typeName, string id, Hashtable values)
		{
			EnsureDataSchema();
			_database.EnsureDatabaseExists();
			if (!_database.VerifyTableExists(_dataSchema, GetTableName(typeName)))
			{
				_database.CreateContentTable(_dataSchema, GetTableName(typeName), _configurationManager.DataMapping.GetTypeFields("", typeName) as IEnumerable<Field>);
			}
			else
			{
				_database.UpdateContentTable(_dataSchema, GetTableName(typeName), _configurationManager.DataMapping.GetTypeFields("", typeName) as IEnumerable<Field>);
			}

			if (!_database.UpdateItem(_dataSchema, GetTableName(typeName), id, values))
			{
				_database.InsertItem(_dataSchema, GetTableName(typeName), values);
			}

			return true;
		}

		public bool Update(string typeName, string id, Hashtable values, params object[] extendedParameters)
		{
			EnsureDataSchema();
			return Update(typeName, id, values);
		}

		#endregion

		private void EnsureDataSchema()
		{
			if (String.IsNullOrEmpty(_dataSchema))
			{
				_dataSchema = ((XmlSqlDataMapping)_configurationManager.DataMapping).GetDataSchema();

				if (!_database.VerifySchemaExists(_dataSchema))
				{
					_database.CreateSchema(_dataSchema);
				}
			}
		}

		private string GetTableName(string typeName)
		{
			return Regex.Replace(typeName, "[\\W\\s]", "");
		}
	}
}
